﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Text;
using System.Xml;
using Lime49;
using ResourceBlender.Core;

namespace ResourceBlender.DAL.Import
{
    public class XmlBackupResourceImporter
    {
        /// <summary>
        /// Imports from a ResourceBlender XML backup.
        /// </summary>
        /// <param name="log">A delegate to which log message should be sent.</param>
        /// <param name="doc">The document to import.</param>
        /// <param name="clearDatabase"><c>true</c> if the database should be erased before starting (if elements are present in that table)</param>
        /// <param name="includeApps"><c>true</c> to include applications, otherwise<c>false</c></param>
        /// <param name="includeElements"><c>true</c> to include elements, otherwise<c>false</c></param>
        /// <param name="includeResources"><c>true</c> to include resources, otherwise<c>false</c></param>
        /// <param name="includeUsers"><c>true</c> to include users, otherwise<c>false</c></param>
        /// <param name="includeTemplates"><c>true</c> to include templates, otherwise<c>false</c></param>
        public static void Import(StatusMessage.StatusChangedEventHandler log, XmlDocument doc, bool clearDatabase, bool includeApps, bool includeElements, bool includeResources, bool includeUsers, bool includeTemplates)
        {
            int resApps = 0,
                resBundles = 0,
                resElements = 0,
                resResources = 0,
                resTemplates = 0,
                resTranslations = 0,
                resUsers = 0;
            Database db = null;
            try
            {
                db = new Database();
                db.OpenConnection();
                if (clearDatabase)
                {
                    try
                    {
                        db.DropTables(includeApps, includeElements, includeResources, includeResources, includeUsers, includeTemplates);
                    }
                    catch { }
                    db.CreateTables(includeApps, includeElements, includeResources, includeResources, includeUsers, includeTemplates);
                }
                using (DbTransaction trans = db.BeginTransaction())
                {
                    if (includeApps)
                    {
                        log(new StatusMessage(string.Format(I18nUtils.GetString("UIStrings", "RestoringName"), I18nUtils.GetString("UIStrings", "Applications")), StatusMessage.StatusMessageType.Status));
                        using (Command cmd = db.CreateCommand(trans))
                        {
                            cmd.CommandText = "INSERT INTO " + db.TablePrefix + "applications (id, name, displayname, defaultlanguage, languages) VALUES (@id, @name, @displayname, @defaultlanguage, @languages)";
                            cmd.AddParameterWithValue("@id", string.Empty);
                            cmd.AddParameterWithValue("@name", string.Empty);
                            cmd.AddParameterWithValue("@displayname", string.Empty);
                            cmd.AddParameterWithValue("@defaultlanguage", string.Empty);
                            cmd.AddParameterWithValue("@languages", string.Empty);
                            XmlNodeList appsNodes = doc.GetElementsByTagName("applications")[0].ChildNodes;
                            foreach (XmlNode app in appsNodes)
                            {
                                string id = string.Empty,
                                       name = string.Empty,
                                       displayname = string.Empty,
                                       defaultlanguage = string.Empty,
                                       languages = string.Empty;
                                foreach (XmlNode property in app)
                                {
                                    switch (property.Name.ToLower())
                                    {
                                        case "id":
                                            id = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "name":
                                            name = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "displayname":
                                            displayname = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "defaultlanguage":
                                            defaultlanguage = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "languages":
                                            languages = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                    }
                                }
                                if (string.IsNullOrEmpty(id) ||
                                   string.IsNullOrEmpty(name) ||
                                   string.IsNullOrEmpty(displayname) ||
                                   string.IsNullOrEmpty(defaultlanguage) ||
                                   string.IsNullOrEmpty(languages))
                                {
                                    log(new StatusMessage("Invalid application " + displayname, StatusMessage.StatusMessageType.Error));
                                }
                                else
                                {
                                    cmd.Parameters["@id"].Value = id;
                                    cmd.Parameters["@name"].Value = name;
                                    cmd.Parameters["@displayname"].Value = displayname;
                                    cmd.Parameters["@defaultlanguage"].Value = defaultlanguage;
                                    cmd.Parameters["@languages"].Value = languages;
                                    try
                                    {
                                        cmd.ExecuteNonQuery();
                                        resApps++;
                                    }
                                    catch (Exception ex)
                                    {
                                        if (ex.Message.ToLowerInvariant().Contains("unique"))
                                        {
                                            log(new StatusMessage(I18nUtils.GetString("Errors", "ApplicationExists") + " - " + name, StatusMessage.StatusMessageType.Error));
                                        }
                                        else
                                        {
                                            log(new StatusMessage(ex.Message, StatusMessage.StatusMessageType.Error));
                                        }
                                    }
                                }
                            }
                        }
                        log(new StatusMessage(string.Format(I18nUtils.GetString("UIStrings", "RestoringName"), I18nUtils.GetString("UIStrings", "Bundles")), StatusMessage.StatusMessageType.Status));
                        using (Command cmd = db.CreateCommand(trans))
                        {
                            cmd.CommandText = "INSERT INTO " + db.TablePrefix + "bundles (id, name, application) VALUES (@id, @name, @application)";
                            cmd.AddParameterWithValue("@id", string.Empty);
                            cmd.AddParameterWithValue("@name", string.Empty);
                            cmd.AddParameterWithValue("@application", string.Empty);
                            XmlNodeList bundlesNodes = doc.GetElementsByTagName("bundles")[0].ChildNodes;
                            foreach (XmlNode bundle in bundlesNodes)
                            {
                                string id = string.Empty,
                                       name = string.Empty,
                                       application = string.Empty;
                                foreach (XmlNode property in bundle)
                                {
                                    switch (property.Name.ToLower())
                                    {
                                        case "id":
                                            id = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "name":
                                            name = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "application":
                                            application = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                    }
                                }
                                if (string.IsNullOrEmpty(id) ||
                                   string.IsNullOrEmpty(name) ||
                                   string.IsNullOrEmpty(application))
                                {
                                    log(new StatusMessage("Invalid bundle " + name, StatusMessage.StatusMessageType.Error));
                                }
                                else
                                {
                                    cmd.Parameters["@id"].Value = id;
                                    cmd.Parameters["@name"].Value = name;
                                    cmd.Parameters["@application"].Value = application;
                                    try
                                    {
                                        cmd.ExecuteNonQuery();
                                        resBundles++;
                                    }
                                    catch (Exception ex)
                                    {
                                        if (ex.Message.ToLowerInvariant().Contains("unique"))
                                        {
                                            log(new StatusMessage(I18nUtils.GetString("Errors", "BundleAlreadyExists") + " - " + name, StatusMessage.StatusMessageType.Error));
                                        }
                                        else
                                        {
                                            log(new StatusMessage(ex.Message, StatusMessage.StatusMessageType.Error));
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (includeElements)
                    {
                        log(new StatusMessage(string.Format(I18nUtils.GetString("UIStrings", "RestoringName"), I18nUtils.GetString("UIStrings", "Elements")), StatusMessage.StatusMessageType.Status));
                        using (Command cmd = db.CreateCommand(trans))
                        {
                            cmd.CommandText = "INSERT INTO " + db.TablePrefix + "elements (applicationid, bundle, elementname, resourceid, comment) VALUES (@applicationid, @bundle, @elementname, @resourceid, @comment)";
                            cmd.AddParameterWithValue("@applicationid", string.Empty);
                            cmd.AddParameterWithValue("@bundle", string.Empty);
                            cmd.AddParameterWithValue("@elementname", string.Empty);
                            cmd.AddParameterWithValue("@resourceid", string.Empty);
                            cmd.AddParameterWithValue("@comment", string.Empty);
                            XmlNodeList elementsNodes = doc.GetElementsByTagName("element");
                            int numNodes = elementsNodes.Count,
                                logIncrements = numNodes >= 5 ? numNodes / 5 : 1;
                            foreach (XmlNode element in elementsNodes)
                            {
                                string applicationid = string.Empty,
                                       bundle = string.Empty,
                                       elementname = string.Empty,
                                       resourceid = string.Empty,
                                       comment = string.Empty;
                                foreach (XmlNode property in element)
                                {
                                    switch (property.Name.ToLower())
                                    {
                                        case "applicationid":
                                            applicationid = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "bundle":
                                            bundle = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "elementname":
                                            elementname = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "resourceid":
                                        case "translationid":
                                            resourceid = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "comment":
                                            comment = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                    }
                                }
                                if (string.IsNullOrEmpty(applicationid) ||
                                   string.IsNullOrEmpty(elementname))
                                {
                                    log(new StatusMessage("Invalid element " + elementname, StatusMessage.StatusMessageType.Error));
                                }
                                else
                                {
                                    cmd.Parameters["@applicationid"].Value = applicationid;
                                    cmd.Parameters["@bundle"].Value = bundle;
                                    cmd.Parameters["@elementname"].Value = elementname;
                                    cmd.Parameters["@resourceid"].Value = resourceid;
                                    cmd.Parameters["@comment"].Value = comment;
                                    try
                                    {
                                        cmd.ExecuteNonQuery();
                                        resElements++;
                                        if (logIncrements > 0 && resElements % logIncrements == 0)
                                        {
                                            log(new StatusMessage(string.Format(I18nUtils.GetString("UIStrings", "RestoredElementsProgress"), resElements, numNodes), StatusMessage.StatusMessageType.Status));
                                        }
                                    }
                                    catch (Exception ex)
                                    {
                                        if (ex.Message.ToLowerInvariant().Contains("unique"))
                                        {
                                            log(new StatusMessage(I18nUtils.GetString("Errors", "ElementNameExists") + " - " + elementname, StatusMessage.StatusMessageType.Error));
                                        }
                                        else
                                        {
                                            log(new StatusMessage(ex.Message, StatusMessage.StatusMessageType.Error));
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (includeResources)
                    {
                        log(new StatusMessage(string.Format(I18nUtils.GetString("UIStrings", "RestoringName"), I18nUtils.GetString("UIStrings", "Resources")), StatusMessage.StatusMessageType.Status));
                        using (Command cmdInsertResource = db.CreateCommand(trans))
                        using (Command cmdInsertComment = db.CreateCommand(trans))
                        {
                            cmdInsertResource.CommandText = "INSERT INTO " + db.TablePrefix + "resources (resourceid, language, translation, translatorcomment) VALUES (@id, @language, @translation, @comment)";
                            cmdInsertResource.AddParameterWithValue("@id", string.Empty);
                            cmdInsertResource.AddParameterWithValue("@language", string.Empty);
                            cmdInsertResource.AddParameterWithValue("@translation", string.Empty);
                            cmdInsertResource.AddParameterWithValue("@comment", string.Empty);
                            cmdInsertComment.CommandText = "INSERT INTO " + db.TablePrefix + "resourcecomments (resourceid, comment) VALUES (@id, @comment)";
                            cmdInsertComment.AddParameterWithValue("@id", string.Empty);
                            cmdInsertComment.AddParameterWithValue("@comment", string.Empty);
                            List<XmlNode> resourceNodes = new List<XmlNode>(); // xml format changed in > 1.3
                            foreach (XmlNode oldNode in doc.GetElementsByTagName("resource"))
                            {
                                resourceNodes.Add(oldNode);
                            }
                            foreach (XmlNode oldNode in doc.GetElementsByTagName("translation"))
                            {
                                resourceNodes.Add(oldNode);
                            }
                            int numNodes = resourceNodes.Count,
                                logIncrements = numNodes >= 10 ? numNodes / 10 : 1;
                            foreach (XmlNode resource in resourceNodes)
                            {
                                try
                                {
                                    string id = resource["id"].InnerText,
                                           comment = HtmlUtils.HtmlDecode(resource["comment"].InnerText),
                                           language = string.Empty;
                                    XmlNodeList ndLanguages = resource.SelectNodes("languages/language");
                                    if (string.IsNullOrEmpty(id) || ndLanguages.Count == 0)
                                    {
                                        log(new StatusMessage("Invalid resource", StatusMessage.StatusMessageType.Error));
                                        continue;
                                    }
                                    if (!string.IsNullOrEmpty(comment))
                                    {
                                        cmdInsertComment.Parameters["@id"].Value = id;
                                        cmdInsertComment.Parameters["@comment"].Value = comment;
                                        cmdInsertComment.ExecuteNonQuery();
                                    }
                                    cmdInsertResource.Parameters["@id"].Value = id;
                                    foreach (XmlNode ndLanguage in ndLanguages)
                                    {
                                        if (ndLanguage.Attributes.Count > 0)
                                        {
                                            cmdInsertResource.Parameters["@language"].Value = ndLanguage.Attributes[0].Value;
                                            cmdInsertResource.Parameters["@translation"].Value = HtmlUtils.HtmlDecode(ndLanguage.InnerText);
                                            if (ndLanguage.Attributes.Count > 1)
                                            {
                                                cmdInsertResource.Parameters["@comment"].Value = HtmlUtils.HtmlDecode(ndLanguage.Attributes[1].InnerText);
                                            }
                                            else
                                            {
                                                cmdInsertResource.Parameters["@comment"].Value = DBNull.Value;
                                            }
                                            try
                                            {
                                                cmdInsertResource.ExecuteNonQuery();
                                                resTranslations++;
                                            }
                                            catch (Exception ex)
                                            {
                                                log(new StatusMessage(string.Format(I18nUtils.GetString("Errors", "ErrorAddTranslationResourceNum"), id, ex.Message), StatusMessage.StatusMessageType.Error));
                                            }
                                        }
                                    }
                                    resResources++;
                                    if (logIncrements > 0 && resResources % logIncrements == 0)
                                    {
                                        log(new StatusMessage(string.Format(I18nUtils.GetString("UIStrings", "RestoredResourcesProgress"), resResources, numNodes), StatusMessage.StatusMessageType.Status));
                                    }
                                }
                                catch (Exception ex)
                                {
                                    log(new StatusMessage("Invalid resource" + ex.Message, StatusMessage.StatusMessageType.Error));
                                }
                            }
                        }
                    }
                    if (includeTemplates)
                    {
                        log(new StatusMessage(string.Format(I18nUtils.GetString("UIStrings", "RestoringName"), I18nUtils.GetString("UIStrings", "Templates")), StatusMessage.StatusMessageType.Status));
                        using (Command cmd = db.CreateCommand(trans))
                        {
                            cmd.CommandText = "INSERT INTO " + db.TablePrefix + "templates (name, filename, filestart, fileend, line) VALUES (@name, @filename, @filestart, @fileend, @line)";
                            cmd.AddParameterWithValue("@name", string.Empty);
                            cmd.AddParameterWithValue("@filename", string.Empty);
                            cmd.AddParameterWithValue("@filestart", string.Empty);
                            cmd.AddParameterWithValue("@fileend", string.Empty);
                            cmd.AddParameterWithValue("@line", string.Empty);
                            XmlNodeList templatesNodes = doc.GetElementsByTagName("template");
                            foreach (XmlNode app in templatesNodes)
                            {
                                string name = string.Empty,
                                       filename = string.Empty,
                                       filestart = string.Empty,
                                       fileend = string.Empty,
                                       line = string.Empty;
                                foreach (XmlNode property in app)
                                {
                                    switch (property.Name.ToLower())
                                    {
                                        case "name":
                                            name = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "filename":
                                            filename = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "filestart":
                                            filestart = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "fileend":
                                            fileend = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "line":
                                            line = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                    }
                                }
                                if (string.IsNullOrEmpty(name) ||
                                   string.IsNullOrEmpty(filename) ||
                                   string.IsNullOrEmpty(line))
                                {
                                    log(new StatusMessage("Invalid template " + name, StatusMessage.StatusMessageType.Error));
                                }
                                else
                                {
                                    cmd.Parameters["@name"].Value = name;
                                    cmd.Parameters["@filename"].Value = filename;
                                    cmd.Parameters["@filestart"].Value = filestart;
                                    cmd.Parameters["@fileend"].Value = fileend;
                                    cmd.Parameters["@line"].Value = line;
                                    try
                                    {
                                        cmd.ExecuteNonQuery();
                                        resTemplates++;
                                    }
                                    catch (Exception ex)
                                    {
                                        if (ex.Message.ToLowerInvariant().Contains("unique"))
                                        {
                                            log(new StatusMessage(I18nUtils.GetString("Errors", "TemplateAlreadyExists") + " - " + name, StatusMessage.StatusMessageType.Error));
                                        }
                                        else
                                        {
                                            log(new StatusMessage(ex.Message, StatusMessage.StatusMessageType.Error));
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if (includeUsers)
                    {
                        log(new StatusMessage(string.Format(I18nUtils.GetString("UIStrings", "RestoringName"), I18nUtils.GetString("UIStrings", "Users")), StatusMessage.StatusMessageType.Status));
                        using (Command cmd = db.CreateCommand(trans))
                        {
                            cmd.CommandText = "INSERT INTO " + db.TablePrefix + "users (username, password, email, permissions, allowedprojects) VALUES (@username, @password, @email, @permissions, @allowedprojects)";
                            cmd.AddParameterWithValue("@username", string.Empty);
                            cmd.AddParameterWithValue("@password", Utils.SaltPassword("password"));
                            cmd.AddParameterWithValue("@email", string.Empty);
                            cmd.AddParameterWithValue("@permissions", string.Empty);
                            cmd.AddParameterWithValue("@allowedprojects", string.Empty);
                            XmlNodeList appsNodes = doc.GetElementsByTagName("user");
                            foreach (XmlNode app in appsNodes)
                            {
                                string username = string.Empty,
                                       email = string.Empty,
                                       permissions = string.Empty,
                                       allowedprojects = string.Empty;
                                foreach (XmlNode property in app)
                                {
                                    switch (property.Name.ToLower())
                                    {
                                        case "username":
                                            username = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "password":
                                            break;
                                        case "email":
                                            email = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "permissions":
                                            permissions = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                        case "allowedprojects":
                                            allowedprojects = HtmlUtils.HtmlDecode(property.InnerText);
                                            break;
                                    }
                                }
                                if (string.IsNullOrEmpty(username) ||
                                   string.IsNullOrEmpty(permissions))
                                {
                                    log(new StatusMessage("Invalid user " + username, StatusMessage.StatusMessageType.Error));
                                }
                                else
                                {
                                    cmd.Parameters["@username"].Value = username;
                                    cmd.Parameters["@email"].Value = email;
                                    cmd.Parameters["@permissions"].Value = permissions;
                                    cmd.Parameters["@allowedprojects"].Value = allowedprojects;
                                    try
                                    {
                                        cmd.ExecuteNonQuery();
                                        resUsers++;
                                    }
                                    catch (Exception ex)
                                    {
                                        if (ex.Message.ToLowerInvariant().Contains("unique"))
                                        {
                                            log(new StatusMessage(I18nUtils.GetString("Errors", "UserAlreadyExists") + " - " + username, StatusMessage.StatusMessageType.Error));
                                        }
                                        else
                                        {
                                            log(new StatusMessage(ex.Message, StatusMessage.StatusMessageType.Error));
                                        }
                                    }
                                }
                            }
                        }
                    }
                    //todo: make numbers in the string bold
                    log(new StatusMessage("\n" + string.Format(I18nUtils.GetString("UIStrings", "RestoredSummary"), resApps, resBundles, resElements, resTemplates, resResources, resTranslations), StatusMessage.StatusMessageType.Status));
                    log(new StatusMessage(I18nUtils.GetString("UIStrings", "RestoreComplete"), StatusMessage.StatusMessageType.Success));
                    trans.Commit();
                }
                db.CloseConnection();
            }
            catch (Exception ex)
            {
                log(new StatusMessage(string.Format(I18nUtils.GetString("Errors", "ErrorNumDescription"), "406", ex.Message), StatusMessage.StatusMessageType.Error));
            }
            finally
            {
                if (db != null && db.Connected)
                {
                    try
                    {
                        db.CloseConnection();
                    }
                    catch { }
                }
            }
        }
    }
}
